﻿using SoundEngine3.AudioProcessing.Algorithms;
using System;

namespace DefaultDspPluginsLib.Depricated
{
    public class Effect_Reverb
    {
        #region default values

        const int numcombs = 8;
        const int numallpasses = 4;
        const double muted = 0;
        const double fixedgain = 0.015;
        const double scalewet = 3;
        const double scaledry = 2;
        const double scaledamp = 0.4;
        const double scaleroom = 0.28;
        const double offsetroom = 0.7;
        const double freezemode = 0.5;
        const int stereospread = 23;

        // These values assume 44.1KHz sample rate
        // they will probably be OK for 48KHz sample rate
        // but would need scaling for 96KHz (or other) sample rates.
        // The values were obtained by listening tests.
        const int combtuningL1 = 1116;
        const int combtuningR1 = 1116 + stereospread;
        const int combtuningL2 = 1188;
        const int combtuningR2 = 1188 + stereospread;
        const int combtuningL3 = 1277;
        const int combtuningR3 = 1277 + stereospread;
        const int combtuningL4 = 1356;
        const int combtuningR4 = 1356 + stereospread;
        const int combtuningL5 = 1422;
        const int combtuningR5 = 1422 + stereospread;
        const int combtuningL6 = 1491;
        const int combtuningR6 = 1491 + stereospread;
        const int combtuningL7 = 1557;
        const int combtuningR7 = 1557 + stereospread;
        const int combtuningL8 = 1617;
        const int combtuningR8 = 1617 + stereospread;
        const int allpasstuningL1 = 556;
        const int allpasstuningR1 = 556 + stereospread;
        const int allpasstuningL2 = 441;
        const int allpasstuningR2 = 441 + stereospread;
        const int allpasstuningL3 = 341;
        const int allpasstuningR3 = 341 + stereospread;
        const int allpasstuningL4 = 225;
        const int allpasstuningR4 = 225 + stereospread;
        #endregion
        public struct allpass
        {
            public double feedback;
            public double[] buffer;
            public int bufsize;
            public int bufidx;

            public void setbuffer(double[] buf, int size)
            {
                buffer = buf;
                bufsize = size;
            }

            public void mute()
            {
                for (int i = 0; i < bufsize; i++)
                    buffer[i] = 0;
            }

            public void setfeedback(double val)
            {
                feedback = val;
            }

            public double getfeedback()
            {
                return feedback;
            }

            public void process(ref double input)
            {
                double bufout = buffer[bufidx];
                //Undenormalise.undenormalise(ref bufout);
                if (bufout < 0.0000001 && bufout > -0.0000001) bufout = 0.0;

                double _output = -input + bufout;
                buffer[bufidx] = input + (bufout * feedback);

                if (++bufidx >= bufsize) bufidx = 0;

                input = _output;
            }

            public void cleanAudio()
            {
                for (int i = 0; i < buffer.Length; ++i)
                {
                    if (buffer[i] < 0.0000001 && buffer[i] > -0.0000001) buffer[i] = 0.0;
                }
            }

            public void clear()
            {
                Array.Clear(buffer, 0, buffer.Length);
            }
        }

        public struct comb
        {
            public double feedback;
            public double filterstore;
            public double damp1;
            public double damp2;
            public double[] buffer;
            public int bufsize;
            public int bufidx;

            public void setbuffer(double[] buf, int size)
            {
                buffer = buf;
                bufsize = size;
            }

            public void mute()
            {
                for (int i = 0; i < bufsize; i++)
                    buffer[i] = 0;
            }

            public void setdamp(double val)
            {
                damp1 = val;
                damp2 = 1 - val;
            }

            public double getdamp()
            {
                return damp1;
            }

            public void setfeedback(double val)
            {
                feedback = val;
            }

            public double getfeedback()
            {
                return feedback;
            }

            public double process(ref double input)
            {
                double output = buffer[bufidx];
                //Undenormalise.undenormalise(ref output);
                if (output < 0.0000001 && output > -0.0000001) output = 0.0;

                filterstore = (output * damp2) + (filterstore * damp1);
                //Undenormalise.undenormalise(ref filterstore);
                if (filterstore < 0.0000001 && filterstore > -0.0000001) filterstore = 0.0;

                buffer[bufidx] = input + (filterstore * feedback);

                if (++bufidx >= bufsize) bufidx = 0;

                return output;
            }

            public void cleanAudio()
            {
                for (int i = 0; i < buffer.Length; ++i)
                {
                    if (buffer[i] < 0.0000001 && buffer[i] > -0.0000001) buffer[i] = 0.0;
                }
            }

            public void clear()
            {
                Array.Clear(buffer, 0, buffer.Length);
            }
        }

        internal void Setup(double roomsSize, double damp, double wet, double dry, double width, int mode)
        {
            this.RoomsSize = roomsSize;
            this.Damp = damp;
            this.Wet = wet;
            this.Dry = dry;
            this.Width = width;
            this.Mode = mode;
            Update();
        }

        // Comb filters
        comb[] combL = new comb[numcombs];
        comb[] combR = new comb[numcombs];

        // Allpass filters
        allpass[] allpassL = new allpass[numallpasses];
        allpass[] allpassR = new allpass[numallpasses];

        // Buffers for the combs
        double[] bufcombL1 = new double[combtuningL1];
        double[] bufcombR1 = new double[combtuningR1];
        double[] bufcombL2 = new double[combtuningL2];
        double[] bufcombR2 = new double[combtuningR2];
        double[] bufcombL3 = new double[combtuningL3];
        double[] bufcombR3 = new double[combtuningR3];
        double[] bufcombL4 = new double[combtuningL4];
        double[] bufcombR4 = new double[combtuningR4];
        double[] bufcombL5 = new double[combtuningL5];
        double[] bufcombR5 = new double[combtuningR5];
        double[] bufcombL6 = new double[combtuningL6];
        double[] bufcombR6 = new double[combtuningR6];
        double[] bufcombL7 = new double[combtuningL7];
        double[] bufcombR7 = new double[combtuningR7];
        double[] bufcombL8 = new double[combtuningL8];
        double[] bufcombR8 = new double[combtuningR8];

        // Buffers for the allpasses
        double[] bufallpassL1 = new double[allpasstuningL1];
        double[] bufallpassR1 = new double[allpasstuningR1];
        double[] bufallpassL2 = new double[allpasstuningL2];
        double[] bufallpassR2 = new double[allpasstuningR2];
        double[] bufallpassL3 = new double[allpasstuningL3];
        double[] bufallpassR3 = new double[allpasstuningR3];
        double[] bufallpassL4 = new double[allpasstuningL4];
        double[] bufallpassR4 = new double[allpasstuningR4];

        public Effect_Reverb()
        {
            // Tie the components to their buffers
            combL[0].setbuffer(bufcombL1, combtuningL1);
            combR[0].setbuffer(bufcombR1, combtuningR1);
            combL[1].setbuffer(bufcombL2, combtuningL2);
            combR[1].setbuffer(bufcombR2, combtuningR2);
            combL[2].setbuffer(bufcombL3, combtuningL3);
            combR[2].setbuffer(bufcombR3, combtuningR3);
            combL[3].setbuffer(bufcombL4, combtuningL4);
            combR[3].setbuffer(bufcombR4, combtuningR4);
            combL[4].setbuffer(bufcombL5, combtuningL5);
            combR[4].setbuffer(bufcombR5, combtuningR5);
            combL[5].setbuffer(bufcombL6, combtuningL6);
            combR[5].setbuffer(bufcombR6, combtuningR6);
            combL[6].setbuffer(bufcombL7, combtuningL7);
            combR[6].setbuffer(bufcombR7, combtuningR7);
            combL[7].setbuffer(bufcombL8, combtuningL8);
            combR[7].setbuffer(bufcombR8, combtuningR8);
            allpassL[0].setbuffer(bufallpassL1, allpasstuningL1);
            allpassR[0].setbuffer(bufallpassR1, allpasstuningR1);
            allpassL[1].setbuffer(bufallpassL2, allpasstuningL2);
            allpassR[1].setbuffer(bufallpassR2, allpasstuningR2);
            allpassL[2].setbuffer(bufallpassL3, allpasstuningL3);
            allpassR[2].setbuffer(bufallpassR3, allpasstuningR3);
            allpassL[3].setbuffer(bufallpassL4, allpasstuningL4);
            allpassR[3].setbuffer(bufallpassR4, allpasstuningR4);

            // Set default values
            allpassL[0].setfeedback(0.5f);
            allpassR[0].setfeedback(0.5f);
            allpassL[1].setfeedback(0.5f);
            allpassR[1].setfeedback(0.5f);
            allpassL[2].setfeedback(0.5f);
            allpassR[2].setfeedback(0.5f);
            allpassL[3].setfeedback(0.5f);
            allpassR[3].setfeedback(0.5f);

        }

        public void Mute()
        {
            if (Mode >= freezemode)
                return;

            int i;
            for (i = 0; i < numcombs; i++)
            {
                combL[i].mute();
                combR[i].mute();
            }
            for (i = 0; i < numallpasses; i++)
            {
                allpassL[i].mute();
                allpassR[i].mute();
            }
        }

        // Recalculate internal values after parameter change
        public void Update()
        {
            int i;

            wet1 = wet * (width / 2 + 0.5f);
            wet2 = wet * ((1 - width) / 2);

            if (mode >= freezemode)
            {
                roomsize1 = 1;
                damp1 = 0;
                gain = muted;
            }
            else
            {
                roomsize1 = roomsize;
                damp1 = damp;
                gain = fixedgain;
            }

            for (i = 0; i < numcombs; i++)
            {
                combL[i].setfeedback(roomsize1);
                combR[i].setfeedback(roomsize1);
            }

            for (i = 0; i < numcombs; i++)
            {
                combL[i].setdamp(damp1);
                combR[i].setdamp(damp1);
            }
        }

        double gain;
        double roomsize, roomsize1;
        double damp, damp1;
        double wet, wet1, wet2;
        double dry;
        double width;
        double mode;

        private double mRoomsSize;
        private double mDamp;
        private double mWet;
        private double mDry;
        private double mWidth;
        private int mMode;

        /// <summary>
        /// Get or set the room size.
        /// </summary>
        public double RoomsSize
        {
            get { return mRoomsSize; }
            set
            {
                mRoomsSize = value;
                roomsize = (value * scaleroom) + offsetroom;
            }
        }

        public double Damp
        {
            get { return mDamp; }
            set
            {
                mDamp = value;
                damp = value * scaledamp;
            }
        }

        public double Wet
        {
            get { return mWet; }
            set
            {
                mWet = value;
                wet = value * scalewet;
            }
        }

        public double Dry
        {
            get { return mDry; }
            set
            {
                mDry = value;
                dry = value * scaledry;
            }
        }

        public double Width
        {
            get { return mWidth; }
            set
            {
                mWidth = value;
                width = value;
            }
        }

        public int Mode
        {
            get { return mMode; }
            set
            {
                if (value < 0 || value > 1)
                {
                    throw new IndexOutOfRangeException("Mode can only be 0 or 1.");
                }
                mMode = value;
                mode = value;
            }
        }

        public void ClearReverb()
        {
            combL[0].clear();
            combL[0].clear();
            combR[0].clear();
            combL[1].clear();
            combR[1].clear();
            combL[2].clear();
            combR[2].clear();
            combL[3].clear();
            combR[3].clear();
            combL[4].clear();
            combR[4].clear();
            combL[5].clear();
            combR[5].clear();
            combL[6].clear();
            combR[6].clear();
            combL[7].clear();
            combR[7].clear();
            allpassL[0].clear();
            allpassR[0].clear();
            allpassL[1].clear();
            allpassR[1].clear();
            allpassL[2].clear();
            allpassR[2].clear();
            allpassL[3].clear();
            allpassR[3].clear();
        }

        IAudioPolationSmoother smoother = new AudioPolationSmoother(2000);

        public void Process(ref ZquenceStudio.DefaultDspPlugins.Effects.Reverb.ReverbData data,
            double[] wav_in_left, double[] wav_in_right, double[] wav_out_left, double[] wav_out_right)
        {
            double outL, outR, input;
            double inputL;
            double inputR;
            int i;

            int channels = data.channels;

            if (channels == 2)
            {
                for (int l = 0; l < data.Length; l++)
                {
                    inputL = wav_in_left[l];
                    inputR = wav_in_right[l];

                    outL = outR = 0;
                    input = (inputL + inputR) * gain;

                    // Accumulate comb filters in parallel
                    for (i = 0; i < 8; i++)
                    {
                        outL += combL[i].process(ref input);
                        outR += combR[i].process(ref input);
                    }

                    // Feed through allpasses in series
                    for (i = 0; i < numallpasses; i++)
                    {
                        allpassL[i].process(ref outL);
                        allpassR[i].process(ref outR);
                    }

                    // Calculate output MIXING with anything already there
                    wav_out_left[l] = outL * wet1 + outR * wet2 + inputL * dry;
                    wav_out_right[l] = outR * wet1 + outL * wet2 + inputR * dry;
                }

                smoother.Process(wav_out_left);
                smoother.Process(wav_out_right);
            }
            else
            {
                for (int l = 0; l < data.Length; l++)
                {
                    inputL = wav_in_left[l];

                    outL = outR = 0;
                    input = inputL * gain;

                    // Accumulate comb filters in parallel
                    for (i = 0; i < 8; i++)
                    {
                        outL += combL[i].process(ref input);
                    }

                    // Feed through allpasses in series
                    for (i = 0; i < numallpasses; i++)
                    {
                        allpassL[i].process(ref outL);
                    }

                    // Calculate output MIXING with anything already there
                    wav_out_left[l] = outL * wet1 + outR * wet2 + inputL * dry;
                }
            }
        }
    }
}
